Skip to content

feat(web): notification (and sound) when a thread finish - #7348

Closed
jaakkoob wants to merge 1 commit into
pingdotgg:mainfrom
jaakkoob:feat/thread-completion-notifications
Closed

feat(web): notification (and sound) when a thread finish#7348
jaakkoob wants to merge 1 commit into
pingdotgg:mainfrom
jaakkoob:feat/thread-completion-notifications

Conversation

@jaakkoob

@jaakkoob jaakkoob commented Aug 17, 2026

Copy link
Copy Markdown

We have no way to tell a thread is done right now other than by looking at it. I'm always looking at the t3 code tab every 5 minutes or so to check if a thread is done, because there is no other way of knowing. That's why I think we should add notification. I tested the web implementation in chromium browser (helium), firefox browser (zen) and safari.

Settings → General gains three rows, revealed progressively: Completion notifications (Test + switch), Notification sound, Notification volume.

Written by Claude Opus 5 in Claude Code; the Test button and volume control were implemented by GPT-5.6-sol via Codex CLI, which also reviewed the branch twice.


Note

Low Risk
Client-only UX: browser notifications, local settings, and Web Audio; no auth, server, or data-model changes beyond new optional client settings fields.

Overview
Adds completion notifications so users get a system alert when a thread’s turn finishes while the app is open (tab or desktop), without polling the UI.

Settings → General adds three linked controls: enable notifications (with permission prompt and Test), optional notification sound, and a volume slider with live chime preview. New client settings (threadCompletionNotifications, sound, volume) are defined in contracts and wired through desktop settings tests.

A ThreadCompletionNotifications watcher lives in the app shell (not the chat route) so completions are detected from any screen and remounts don’t reset state. It only subscribes to thread data when notifications are on and permission is granted. deriveThreadCompletionNotifications fires on transitions (running → completed), skips first-seen threads on load, ignores interrupted/error/archived turns, waits for session idle and for subagent backgroundLiveness to clear, and uses turn-scoped notification tags. Clicks focus the window and navigate to the thread.

Supporting pieces: notificationPermission (shared permission store + showNotification), notificationChime (Web Audio two-note chime with gesture priming), unit tests for logic and gain, settings search entries, and user docs at docs/user/notifications.md.

Reviewed by Cursor Bugbot for commit d19d671. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add OS notifications and optional chime sound when a thread turn completes

  • Adds a ThreadCompletionNotifications component mounted in the app shell that watches all threads and fires an OS notification when a turn transitions from running to completed.
  • Adds settings in the General panel to enable/disable completion notifications, request permission, test delivery, toggle sound, and adjust chime volume with a live preview.
  • Implements a Web Audio two-note triangle-wave chime with perceptual volume scaling via playNotificationChime and chimeGainForVolume in notificationChime.ts.
  • Adds useNotificationPermission hook and showNotification util in notificationPermission.ts to manage permission state across the app lifecycle and display click-to-focus OS notifications.
  • Extends ClientSettingsSchema with threadCompletionNotifications, threadCompletionNotificationSound, and threadCompletionNotificationVolume (default off, sound on, volume 70).
📊 Macroscope summarized d19d671. 10 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9cc0ae9e-c9e3-49ce-8158-fd733d19141f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 17, 2026
@jaakkoob

Copy link
Copy Markdown
Author
image

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two consistency findings in the new Settings rows. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +1809 to +1846
<div className="flex w-full items-center gap-3 sm:w-52">
<output
className="min-w-12 rounded-md bg-muted px-2 py-1 text-center font-mono text-xs font-medium tabular-nums text-foreground"
htmlFor="thread-completion-notification-volume-slider"
>
{volume}%
</output>
<input
aria-label="Notification volume"
className="settings-slider min-w-0 flex-1"
id="thread-completion-notification-volume-slider"
max={MAX_NOTIFICATION_VOLUME}
min={MIN_NOTIFICATION_VOLUME}
onChange={(event) => {
const nextVolume = Number(event.currentTarget.value);
if (
!Number.isInteger(nextVolume) ||
nextVolume < MIN_NOTIFICATION_VOLUME ||
nextVolume > MAX_NOTIFICATION_VOLUME
) {
return;
}

primeNotificationChime();
updateSettings({ threadCompletionNotificationVolume: nextVolume });
if (previewTimerRef.current !== null) {
window.clearTimeout(previewTimerRef.current);
}
previewTimerRef.current = window.setTimeout(() => {
previewTimerRef.current = null;
playNotificationChime(nextVolume);
}, 400);
}}
step={10}
style={volumeSliderStyle}
type="range"
value={volume}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reconstructs the glass-opacity slider row (lines 1031-1058): identical output badge classes, identical settings-slider min-w-0 flex-1 input, and a second copy of the --settings-slider-progress / --settings-slider-fill-offset math, which encodes the half-thumb offset the .settings-slider CSS contract in index.css depends on. With two copies, a change to that fill math or to the badge geometry only lands on one slider. Consider extracting a named control next to SettingsRow in settingsLayout.tsx (value, min, max, step, aria-label, id, onChange) and rendering both rows through it, keeping call-site-only concerns such as the chime preview timer here.

Posted via Macroscope — UI Consistency

Comment on lines +1923 to +1933
<Switch
checked={enabled}
onCheckedChange={(checked) => {
if (checked) {
enable();
return;
}
updateSettings({ threadCompletionNotifications: false });
}}
aria-label="Thread completion notifications"
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When permission is denied or unsupported, enable() returns early, so this switch accepts the click, reports no state change, and snaps back — an interactive-looking control that cannot do anything. Switch already carries a disabled contract (data-disabled:cursor-not-allowed, reduced opacity, no keyboard activation), and the row's status text already explains why. Consider disabling it only while it is off and permission cannot be granted, so the documented case (permission revoked after opting in) still leaves a switch the user can turn off.

Suggested change
<Switch
checked={enabled}
onCheckedChange={(checked) => {
if (checked) {
enable();
return;
}
updateSettings({ threadCompletionNotifications: false });
}}
aria-label="Thread completion notifications"
/>
<Switch
checked={enabled}
disabled={!enabled && (permission === "denied" || permission === "unsupported")}
onCheckedChange={(checked) => {
if (checked) {
enable();
return;
}
updateSettings({ threadCompletionNotifications: false });
}}
aria-label="Thread completion notifications"
/>

Posted via Macroscope — UI Consistency

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6c24b9c89ad9718b207759950851abf0a40e9561. Configure here.

showNotification,
useNotificationPermission,
} from "../../notificationPermission";
import { playNotificationChime, primeNotificationChime } from "../../notificationChime";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore skips notification settings

Medium Severity

The new General settings threadCompletionNotifications, threadCompletionNotificationSound, and threadCompletionNotificationVolume are omitted from changedSettingLabels and the restoreDefaults updateSettings payload. Changing them never dirties Restore defaults, and Restore leaves them unchanged while resetting neighboring General rows.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6c24b9c89ad9718b207759950851abf0a40e9561. Configure here.

Comment thread apps/web/src/threadCompletionNotifications.logic.ts
@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a complete new feature (thread completion notifications with audio chimes), adding new components, browser API integrations, settings schema changes, and ~860 lines of new code. New features introducing user-facing behavior warrant human review. Additionally, unresolved Medium-severity findings identify missing restore-defaults handling and a documentation inaccuracy.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@jaakkoob jaakkoob changed the title feat(web): notify when a thread finishes its turn feat(web): notification (and sound) when a thread finish Aug 18, 2026

bj97301 commented Aug 18, 2026

Copy link
Copy Markdown

Huge +1 from me! I’d be genuinely excited to have completion sounds in T3 Code—being able to switch away during a long-running thread and hear when it’s ready would be fantastic. The opt-in sound and volume controls proposed here look especially nice. 🔔

Nothing tells you a thread is done unless you are looking at it, so long
runs mean polling the tab. Web and desktop have no completion notification
at all; only mobile does, and that path needs the relay.

Adds an opt-in client setting that raises a native notification, with an
optional synthesized chime, when a thread's latest turn completes. It
announces a transition rather than a state, so it needs no clock of its
own: a thread is news only once this client has seen it unfinished and
then finished. A thread seen for the first time — on load, on reconnect,
or when an environment joins late — is recorded silently, which is what
keeps a page load from announcing the user's whole history.

Turns that were interrupted or errored are not announced, and neither is a
thread whose `backgroundLiveness` is still `working`, so delegating to
subagents does not read as finishing.

The watcher lives in the app shell so a turn finishing while the user is
in Settings still reaches them, and subscribes to thread updates only
while the setting is on and permission is granted.

Web and desktop only; desktop inherits it through the web bundle. Mobile
is untouched — it already has relay-backed push. Notifications need an
open tab by construction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jaakkoob
jaakkoob force-pushed the feat/thread-completion-notifications branch from 6c24b9c to d19d671 Compare August 19, 2026 22:29

Notifications are delivered by the app itself, so they arrive while T3 Code is open in a tab or in
the desktop app, including when it is in the background. A closed tab receives nothing. On iOS and
Android, the mobile app delivers its own push notifications instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium user/notifications.md:21

Android users are told they will receive completion push notifications, but the mobile app does not deliver them because requestAgentNotificationPermission and SettingsRouteScreen support only iOS. Update this sentence to mention iOS only.

Suggested change
Android, the mobile app delivers its own push notifications instead.
On iOS, the mobile app delivers its own push notifications instead.
🤖 Copy this AI Prompt to have your agent fix this:
In file @docs/user/notifications.md around line 21:

Android users are told they will receive completion push notifications, but the mobile app does not deliver them because `requestAgentNotificationPermission` and `SettingsRouteScreen` support only iOS. Update this sentence to mention iOS only.

@petekblaz

Copy link
Copy Markdown

Its honestly kinda strange we dont have alerts yet. T3 is such a cool app, but some sort of sound notifications when a thread needs input or is done would improve it by a mile.

@Deveshb15

Copy link
Copy Markdown

@t3dotgg @maria-rcks can someone please review this? need alerts and could be turned off by default in settings so only people need it can turn it on, happy to contribute and make this better in this PR itself but please review

@camchis

camchis commented Sep 12, 2026

Copy link
Copy Markdown

This would be super useful, feels like missing basic functionality 🙏

@LukasFB

LukasFB commented Sep 12, 2026

Copy link
Copy Markdown

Another +1 here, this is a no-brainer without any downside and very much needed

@juliusmarminge

Copy link
Copy Markdown
Member

Superseded by #11481 (opt-in thread notifications and sounds on main). Closing as superseded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants